package com.rskj.cashbook.handle;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.rskj.cashbook.service.ISqlMonitorLogService;
import com.rskj.cashbook.util.SnowflakeIdWorker;
import com.rskj.cashbook.vo.SqlTxtMonitorLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
@Slf4j
@Component
public class SqlLogInterceptor implements Interceptor {

    private static final List<String> IGNORE_SET = Arrays.asList(
            "com.rskj.cashbook.dao.SqlTxtMonitorLogDao.insert",
            "com.rskj.cashbook.dao.SqlTxtMonitorLogDao.insertBatch",
            "_COUNT",
            "PageHelper"
    );

    private static final String MAPPEDSTATEMENT_KEY = "delegate.mappedStatement";
    private static final String REALTARGET_KEY = "h.target";
    private static final String BOUNDSQL_KEY = "delegate.boundSql";
    private static final String PARAMS_1_KEY = "param1";

    @Lazy
    @Resource
    private ISqlMonitorLogService sqlMonitorLogService;

    @Resource
    private ObjectMapper objectMapper;

    // 是否显示完整的SQL参数值
    @Value("${sql.monitor.show-full-sql:true}")
    private boolean showFullSql;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = null;
        Exception exception = null;

        try {
            result = invocation.proceed();
            return result;
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            try {
                recordSqlLog(invocation, startTime, result, exception);
            } catch (Exception e) {
                log.error("记录SQL日志失败: {}", e.getMessage(), e);
            }
        }
    }

    private void recordSqlLog(Invocation invocation, long startTime, Object result, Exception exception) {
        Object realTarget = realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(realTarget);
        MappedStatement ms = (MappedStatement) metaObject.getValue(MAPPEDSTATEMENT_KEY);

        // 检查是否需要忽略
        if (IGNORE_SET.stream().anyMatch(ms.getId()::contains)) {
            return;
        }

        long executionDuration = System.currentTimeMillis() - startTime;
        String sql = getSql(metaObject, ms);
        String sqlParams = getSqlParams(metaObject, ms);

        SqlTxtMonitorLog log = new SqlTxtMonitorLog();
        log.setId(SnowflakeIdWorker.getId());
        log.setSqlText(sql);
        log.setSqlParams(sqlParams);
        log.setExecutionTime(LocalDateTime.now());
        log.setExecutionDuration(executionDuration);
        log.setMethodName(ms.getId());
        log.setClassName(getClassName(ms.getId()));
        log.setQueryType(getQueryType(sql));
        log.setStatus(exception != null ? "FAILED" : "SUCCESS");
        log.setErrorMessage(exception != null ? exception.getMessage() : null);
        log.setResultCount(getResultCount(result));
        log.setCallStack(getCallStack());
        log.setApplicationName("cashbook");

        sqlMonitorLogService.saveSqlLogAsync(log);
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    @SuppressWarnings("unchecked")
    public <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue(REALTARGET_KEY));
        }
        return (T) target;
    }

    private String getSql(MetaObject metaObject, MappedStatement ms) {
        BoundSql boundSql = (BoundSql) metaObject.getValue(BOUNDSQL_KEY);
        String sql = boundSql.getSql();
        Object parameterObject = boundSql.getParameterObject();

        if (parameterObject == null) {
            return sql;
        }

        // 如果配置为不显示完整SQL，直接返回原始SQL
        if (!showFullSql) {
            return sql;
        }

        try {
            if (parameterObject instanceof Map<?, ?> paramMap) {
                log.debug("参数是Map类型，内容: {}", paramMap);
                Object value = paramMap.get(PARAMS_1_KEY);
                if (value instanceof List<?> list) {
                    log.debug("参数是List类型，大小: {}", list.size());
                    for (Object o : list) {
                        sql = buildSql(boundSql, o, sql);
                    }
                    return sql;
                }
            }
            return buildSql(boundSql, parameterObject, sql);
        } catch (Exception e) {
            log.warn("构建完整SQL失败，返回原始SQL: {}", e.getMessage());
            return sql;
        }
    }

    private String getSqlParams(MetaObject metaObject, MappedStatement ms) {
        try {
            BoundSql boundSql = (BoundSql) metaObject.getValue(BOUNDSQL_KEY);
            Object parameterObject = boundSql.getParameterObject();

            if (parameterObject == null) {
                return null;
            }

            // 如果是Map类型，直接转换为JSON
            if (parameterObject instanceof Map) {
                return objectMapper.writeValueAsString(parameterObject);
            }

            // 如果是基本类型或包装类型，直接返回字符串
            if (parameterObject instanceof String || 
                parameterObject instanceof Number || 
                parameterObject instanceof Boolean ||
                parameterObject instanceof java.util.Date ||
                parameterObject instanceof java.time.LocalDateTime ||
                parameterObject instanceof java.time.LocalDate) {
                return parameterObject.toString();
            }

            // 如果是其他类型，尝试转换为JSON
            return objectMapper.writeValueAsString(parameterObject);
        } catch (Exception e) {
            log.warn("获取SQL参数失败: {}", e.getMessage());
            return null;
        }
    }

    private String buildSql(BoundSql boundSql, Object parameterObject, String sql) {
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            log.debug("没有参数映射，返回原始SQL");
            return sql;
        }

        log.debug("参数映射数量: {}", parameterMappings.size());
        log.debug("参数对象类型: {}", parameterObject.getClass().getName());
        log.debug("参数对象内容: {}", parameterObject);
        
        // 如果是Map类型，输出所有键值对
        if (parameterObject instanceof Map) {
            Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
            log.debug("参数Map内容:");
            for (Map.Entry<?, ?> entry : paramMap.entrySet()) {
                log.debug("  {} = {}", entry.getKey(), entry.getValue());
            }
        }

        // 创建参数值列表
        List<Object> parameterValues = new ArrayList<>();
        
        for (ParameterMapping parameterMapping : parameterMappings) {
            String parameterName = parameterMapping.getProperty();
            Object parameterValue = null;

            log.debug("处理参数: {}", parameterName);
            log.debug("参数映射详情: property={}, javaType={}, mode={}", 
                     parameterMapping.getProperty(), 
                     parameterMapping.getJavaType(), 
                     parameterMapping.getMode());

            if (IGNORE_SET.stream().anyMatch(parameterName::contains)) {
                log.debug("参数 {} 在忽略列表中，使用占位符", parameterName);
                parameterValues.add("?");
                continue;
            }
            
            // 检查参数对象是否为null
            if (parameterObject == null) {
                log.debug("参数对象为null，使用占位符");
                parameterValues.add("?");
                continue;
            }

            try {
                // 如果参数对象本身就是基本类型，直接使用
                if (parameterObject instanceof String || parameterObject instanceof Number || 
                    parameterObject instanceof Boolean || parameterObject instanceof java.util.Date ||
                    parameterObject instanceof java.time.LocalDateTime || 
                    parameterObject instanceof java.time.LocalDate || 
                    parameterObject instanceof java.time.LocalTime) {
                    parameterValue = parameterObject;
                    log.debug("参数对象是基本类型，直接使用: {}", parameterValue);
                } else {
                    // 处理嵌套属性，如 "user.currentTime"
                    if (parameterName.contains(".")) {
                        String[] parts = parameterName.split("\\.");
                        Object currentObject = parameterObject;
                        
                        for (String part : parts) {
                            if (currentObject == null) {
                                break;
                            }
                            
                            Field field = getFieldFromSuperclass(currentObject.getClass(), part);
                            field.setAccessible(true);
                            currentObject = field.get(currentObject);
                        }
                        parameterValue = currentObject;
                    } else {
                        // 简单属性
                        Field field = getFieldFromSuperclass(parameterObject.getClass(), parameterName);
                        field.setAccessible(true);
                        parameterValue = field.get(parameterObject);
                    }
                }
                
                log.debug("参数 {} 的值: {}", parameterName, parameterValue);
                
                // 如果获取到的值为null，记录调试信息
                if (parameterValue == null) {
                    log.debug("参数 {} 的值为 null", parameterName);
                }
            } catch (Exception e) {
                log.debug("获取参数值失败: Field {} not found, 尝试其他方式获取, 错误: {}", parameterName, e.getMessage());
                
                // 尝试其他方式获取参数值
                try {
                    // 如果是Map类型，尝试直接获取
                    if (parameterObject instanceof Map) {
                        Map<?, ?> paramMap = (Map<?, ?>) parameterObject;
                        parameterValue = paramMap.get(parameterName);
                        log.debug("从Map中获取参数 {} 的值: {}", parameterName, parameterValue);
                    } else {
                        // 尝试使用反射获取所有字段
                        Field[] fields = parameterObject.getClass().getDeclaredFields();
                        for (Field field : fields) {
                            field.setAccessible(true);
                            Object value = field.get(parameterObject);
                            log.debug("字段 {} = {}", field.getName(), value);
                        }
                        parameterValue = "?";
                    }
                } catch (Exception ex) {
                    log.debug("其他方式获取参数值也失败: {}", ex.getMessage());
                    parameterValue = "?";
                }
            }

            parameterValues.add(parameterValue);
        }

        // 替换SQL中的占位符
        String resultSql = sql;
        for (int i = 0; i < parameterValues.size(); i++) {
            Object value = parameterValues.get(i);
            log.debug("替换第{}个参数: {} -> {}", i + 1, "?", value);
            
            if (value == null) {
                resultSql = resultSql.replaceFirst("\\?", "NULL");
            } else if ("?".equals(value)) {
                // 如果是占位符，保持原样
                log.debug("跳过占位符参数");
                continue;
            } else if (value instanceof String) {
                // 字符串类型需要加引号
                String strValue = value.toString().replace("'", "''"); // 转义单引号
                resultSql = resultSql.replaceFirst("\\?", "'" + strValue + "'");
            } else if (value instanceof Number || value instanceof Boolean) {
                // 数字和布尔类型直接替换
                resultSql = resultSql.replaceFirst("\\?", value.toString());
            } else if (value instanceof java.util.Date || value instanceof java.time.LocalDateTime || 
                       value instanceof java.time.LocalDate || value instanceof java.time.LocalTime) {
                // 日期时间类型格式化
                String dateValue = "'" + value.toString() + "'";
                resultSql = resultSql.replaceFirst("\\?", dateValue);
            } else {
                // 其他类型转换为字符串并加引号
                String strValue = value.toString().replace("'", "''");
                resultSql = resultSql.replaceFirst("\\?", "'" + strValue + "'");
            }
        }
        return resultSql;
    }

    private Field getFieldFromSuperclass(Class<?> clazz, String fieldName) throws NoSuchFieldException {
        if (clazz == null) {
            throw new NoSuchFieldException("Class is null");
        }
        
        while (clazz != null && clazz != Object.class) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                return field;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field " + fieldName + " not found in class or its superclasses");
    }

    private String getClassName(String methodName) {
        if (methodName == null) {
            return null;
        }

        int lastDotIndex = methodName.lastIndexOf('.');
        if (lastDotIndex > 0) {
            return methodName.substring(0, lastDotIndex);
        }
        return methodName;
    }

    private String getQueryType(String sql) {
        if (sql == null) {
            return "UNKNOWN";
        }

        String upperSql = sql.trim().toUpperCase();
        if (upperSql.startsWith("SELECT")) {
            return "SELECT";
        } else if (upperSql.startsWith("INSERT")) {
            return "INSERT";
        } else if (upperSql.startsWith("UPDATE")) {
            return "UPDATE";
        } else if (upperSql.startsWith("DELETE")) {
            return "DELETE";
        } else {
            return "OTHER";
        }
    }

    private Integer getResultCount(Object result) {
        if (result == null) {
            return 0;
        }

        if (result instanceof List) {
            return ((List<?>) result).size();
        } else if (result instanceof Map) {
            return 1;
        } else if (result instanceof Number) {
            return ((Number) result).intValue();
        }

        return 1;
    }

    private String getCallStack() {
        try {
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            StringBuilder callStack = new StringBuilder();

            // 跳过前几个栈帧（当前方法、拦截器等）
            for (int i = 4; i < Math.min(stackTrace.length, 10); i++) {
                StackTraceElement element = stackTrace[i];
                if (element.getClassName().startsWith("com.rskj.cashbook")) {
                    callStack.append(element.getClassName())
                            .append(".")
                            .append(element.getMethodName())
                            .append("(")
                            .append(element.getLineNumber())
                            .append(")\n");
                }
            }

            return callStack.toString();
        } catch (Exception e) {
            log.warn("获取调用栈失败: {}", e.getMessage());
            return null;
        }
    }
}
